<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>分片上传</title>
    <script src="js/spark-md5-3.0.2.min.js"></script>
</head>
<body>
<h2>分片上传</h2>

<form enctype="multipart/form-data">
    <input type="file" name="fileInput" id="fileInput">
    <input type="button" value="计算文件MD5" onclick="calculateFileMD5()">
    <input type="button" value="上传" onclick="uploadFile()">
    <input type="button" value="检测文件完整性" onclick="checkFile()">
</form>

<p>
    文件MD5:
    <span id="fileMd5" style="color: red"></span>
</p>
<p>
    上传结果:
    <span id="uploadResult" style="color: red"></span>
</p>
<p>
    检测文件完整性:
    <span id="checkFileRes" style="color: red"></span>
</p>


<script>
    // 每个分片的大小
    var chunkSize = 10 * 1024 * 1024;
    var uploadResult = document.getElementById("uploadResult")
    var fileMd5Span = document.getElementById("fileMd5")
    var checkFileRes = document.getElementById("checkFileRes")
    var fileName = document.getElementById("fileInput").value
    var fileMd5;

    // 计算文件md5值
    function calculateFileMD5() {
        var fileInput = document.getElementById('fileInput');
        var file = fileInput.files[0];
        getFileMd5(file).then((md5) => {
            console.info(md5)
            fileMd5 = md5;
            fileMd5Span.innerHTML = md5;
        })
    }

    // 获取文件md5值
    function getFileMd5(file) {
        return new Promise((resolve, reject) => {
            let fileReader = new FileReader()
            fileReader.onload = function (event) {
                let fileMd5 = SparkMD5.ArrayBuffer.hash(event.target.result)
                resolve(fileMd5)
            }
            fileReader.readAsArrayBuffer(file)
        })
    }

    // 上传单个文件
    function uploadFile() {
        var fileInput = document.getElementById('fileInput');
        var file = fileInput.files[0];

        if (!file) return;
        if (!fileMd5) return;

        // 切割文件，上传每一个文件分片
        let fileArr = this.sliceFile(file);

        fileArr.forEach((e, i) => {
            //创建formdata对象
            let data = new FormData();
            data.append("totalNumber", fileArr.length)
            data.append("chunkSize", chunkSize)
            data.append("chunkNumber", i)
            data.append("md5", fileMd5)
            data.append("file", new File([e], file.name));
            upload(data);
        })
    }

    // 切割文件
    function sliceFile(file) {
        const chunks = [];
        let start = 0;
        let end;
        while (start < file.size) {
            end = Math.min(start + chunkSize, file.size);
            chunks.push(file.slice(start, end));
            start = end;
        }
        return chunks;
    }

    // 调用后台接口，上传每一个文件分片
    function upload(data) {
        var xhr = new XMLHttpRequest();
        // 当上传完成时调用
        xhr.onload = function () {
            if (xhr.status === 200) {
                uploadResult.append('上传成功分片：' + data.get("chunkNumber") + '\t');
            }
        }
        xhr.onerror = function () {
            uploadResult.innerHTML = '上传失败';
        }
        // 发送请求
        // xhr.open('POST', '/uploadBig', true);
        xhr.open('POST', '/breakUpload', true);
        xhr.send(data);
    }

    // 调用后台接口，检查文件完整性
    function checkFile() {
        var xhr = new XMLHttpRequest();
        // 当上传完成时调用
        xhr.onload = function () {
            if (xhr.status === 200) {
                checkFileRes.innerHTML = '检测文件完整性成功:' + xhr.responseText;
            }
        }
        xhr.onerror = function () {
            checkFileRes.innerHTML = '检测文件完整性失败';
        }
        // 发送请求
        xhr.open('POST', '/checkFile', true);
        let data = new FormData();
        data.append("md5", fileMd5)
        xhr.send(data);
    }

    function checkFileAndContinue(file) {
        var xhr = new XMLHttpRequest();
        xhr.open('POST', '/checkFile', true);
        let data = new FormData();
        data.append("md5", fileMd5);
        fileName = file.name;
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);

                    if (response.msg === '文件未上传') {
                        // 文件未上传，开始上传所有分片
                        uploadResult.innerHTML = '此文件未上传，可以上传';
                        uploadFile();
                    } else if (response.msg === '文件md5值不匹配') {
                        uploadResult.innerHTML = '文件MD5校验失败';
                    } else if (response.msg === '文件已上传') {
                        uploadResult.innerHTML = '文件已上传';
                    } else {
                        // 有分片未上传，继续上传未完成的分片
                        // 获取后台返回的文件分片上传状态信息
                        const chunksStatus = response.chucks;

                        let chunksToUploadList = [];
                        let fileArr = sliceFile(file);
                        for (let i = 0; i < chunksStatus.length; i++) {
                            if (chunksStatus.charAt(i) === '0') {
                                // 构造一个分片对象
                                let chunksObj = {
                                    chunksToUpload: [],     // 文件分片数组
                                    chunksNumber: 0     // 分片编号
                                }
                                chunksObj.chunksToUpload.push(fileArr[i]);
                                chunksObj.chunksNumber = i;
                                chunksToUploadList.push(chunksObj);
                            }
                        }
                        if (chunksToUploadList.length > 0) {
                            uploadChunks(chunksToUploadList, fileArr);
                        } else {
                            uploadResult.innerHTML = '所有分片已上传';
                        }
                    }
                } else {
                    uploadResult.innerHTML = '检查文件状态失败';
                }
            }
        };
        xhr.send(data);
    }

    // 上传文件分片
    function uploadChunks(chunksToUploadList, fileArr) {
        chunksToUploadList.forEach((chunksObj, i) => {
            chunksObj.chunksToUpload.forEach((chunk, i) => {
                let data = new FormData();
                data.append("totalNumber", fileArr.length);
                data.append("chunkSize", chunkSize);
                data.append("chunkNumber", chunksObj.chunksNumber);
                data.append("md5", fileMd5);
                data.append("file", new File([chunk], fileName));

                var xhr = new XMLHttpRequest();
                // 当上传完成时调用
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        uploadResult.append('上传成功分片：' + data.get("chunkNumber") + '\t');
                    }
                }
                xhr.onerror = function () {
                    uploadResult.innerHTML = '上传失败';
                }
                // 发送请求
                xhr.open('POST', '/breakUpload', true);
                xhr.send(data);
            });
        })
    }

    // 在用户选择文件后，先计算MD5并检查文件上传状态
    document.getElementById('fileInput').addEventListener('change', function () {
        var fileInput = document.getElementById('fileInput');
        var file = fileInput.files[0];
        if (file) {
            getFileMd5(file).then(md5 => {
                fileMd5 = md5;
                fileMd5Span.innerHTML = md5;
                // 检查文件上传状态
                checkFileAndContinue(file);
            });
        }
    });

</script>

</body>
</html>